#include "mixp_module_manager.h"
#include "json/cJSON.h"

#include <dlfcn.h>

static void load_all_modules(mixp_module_manager_t *);
static int init_flow(mixp_module_manager_t *);
static mixp_module_type_t module_type_by_name(char *);

mixp_module_manager_t *mixp_module_manager_new(mixp_config_t *config)
{
    mixp_module_manager_t *mm = MALLOC(mixp_module_manager_t);

    if (mm != NULL)
    {
        mm->config = config;
        INIT_LIST_HEAD(&mm->modules);
        load_all_modules(mm);

        if (init_flow(mm) != 0)
        {
            FREE(mm);
            return NULL;
        }
    }

    return mm;
}

static void unload_all_modules(mixp_module_manager_t *mm)
{
    mixp_module_t *module, *tmp;
    list_for_each_entry_safe(module, tmp, &mm->modules, entry)
    {
        // list_del(&module->entry);
        mixp_module_free(module);
    }
}

void mixp_module_manager_free(mixp_module_manager_t *mm)
{
    unload_all_modules(mm);
    FREE(mm);
}

mixp_module_t *module_lookup_by_name(mixp_module_manager_t *mm, const char *name)
{
    if (!mm || !name)
        return NULL;

    struct list_head *head = NULL;
    mixp_module_t *module = NULL;

    list_for_each(head, &mm->modules)
    {
        module = list_entry(head, mixp_module_t, entry);
        if (module && 0 == strcmp(module->name, name))
        {
            return module;
        }
    }

    return NULL;
}

mixp_module_t *module_lookup_by_type(mixp_module_manager_t *mm, int type)
{
    if (!mm)
        return NULL;

    struct list_head *head = NULL;
    mixp_module_t *module = NULL;

    list_for_each(head, &mm->modules)
    {
        module = list_entry(head, mixp_module_t, entry);
        if (module && module->module_type == type)
        {
            return module;
        }
    }

    return NULL;
}

static mixp_module_t *load_module(mixp_module_manager_t *mm, const char *path, const char *name)
{
    mixp_module_t *module = NULL;

    void *handle = dlopen(path, RTLD_LAZY);
    if (!handle)
    {
        loge("dlopen failed: %s, path: %s", dlerror(), path);
        goto failed;
    }
    module = module_lookup_by_name(mm, name);
    if (module)
    {
        logw("module: %s has already loaded!\n", name);
        return module;
    }

    module = MALLOC(mixp_module_t);
    if (!module)
    {
        goto failed;
    }

    module->handle = handle;
    module->name = (char *)name;
    module->path = (char *)path;
    list_add(&module->entry, &mm->modules);

    return module;

failed:
    if (handle)
        dlclose(handle);
    if (module)
    {
        free(module);
    }

    return NULL;
}

static void load_all_modules(mixp_module_manager_t *mm)
{
    cJSON *json = (cJSON *)mm->config->priv;
    cJSON *modules = cJSON_GetObjectItem(json, "modules");

    if (modules)
    {
        cJSON *item = NULL;

        cJSON_ArrayForEach(item, modules)
        {
            char *module_name = item->string;
            mixp_module_type_t module_type = module_type_by_name(module_name);

            if (module_type == -1)
            {
                logw("un-supported module type: %s", module_name);
                continue;
            }

            int n_max_workers = cJSON_GetObjectItem(item, "n-workers")->valueint;
            int n_threads = cJSON_GetObjectItem(item, "n-threads")->valueint;
            char *fpath = cJSON_GetObjectItem(item, "fpath")->valuestring;

            mixp_module_t *module = load_module(mm, fpath, module_name);
            if (module != NULL)
            {
                module->module_type = module_type;
                module->n_max_workers = n_max_workers;
                module->n_workers = 0;
                module->n_threads = n_threads;
                module->ops = module_ops_by_type(module_type);

                if (module->ops->load != NULL)
                {
                    module->ops->load(module, item);
                }

                logi("Module '%s' loaded. path: %s, n-workers: %d, n-threads: %d", module_name, fpath, n_max_workers, n_threads);
            }
            else
            {
                logw("load module '%s' failed.", module_name);
            }
        }
    }
    else
    {
        logw("no any 'modules' node in config.");
    }
}

static int init_flow(mixp_module_manager_t *mm)
{
    cJSON *json = (cJSON *)mm->config->priv;
    cJSON *flow = cJSON_GetObjectItem(json, "flow");

    if (flow == NULL || !cJSON_IsArray(flow))
    {
        loge("not found 'flow' in config, or not a array.");
        return -1;
    }

    int size = cJSON_GetArraySize(flow);
    if (size <= 0 || size > MAX_FLOW_STEPS)
    {
        loge("the number of flow must > 0 and <= %d", MAX_FLOW_STEPS);
        return -2;
    }

    cJSON *item = NULL;
    int idx = 0;
    cJSON_ArrayForEach(item, flow)
    {
        char *module_name = item->valuestring;
        mixp_module_t *module = module_lookup_by_name(mm, module_name);
        if (module == NULL)
        {
            loge("no module '%s' config.", module_name);
            return -3;
        }
        else
        {
            mm->flow[idx++] = module;
        }
    }

    return 0;
}

static mixp_module_type_t module_type_by_name(char *name)
{
    if (!strcasecmp("vad", name))
    {
        return MIXP_MODULE_VAD;
    }
    else if (!strcasecmp("post", name))
    {
        return MIXP_MODULE_POST;
    }
    else if (!strcasecmp("audiodecoder", name))
    {
        return MIXP_MODULE_AUDIO_DECODE;
    }
    else
    {
        return -1;
    }
}